package model;


import dao.DaoFactory;
import dao.UserDao;
import dao.DaoException;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;

/**
 * 
 * This class is User model class
 * 
 * The folder class represents a user object with its parameters.
 * @author Sukhjinder K. Narula (narula.sk@gmail.com)
 * @since May 2013
 * @version $Revision: 1.0 $
 */
public class User {
	
	private UserDao dao;
	private int id;
	private String name;
	private List<Folder> folders;
	private List<Token> tokens;
	private List<Template> templates;
	private List<Meeting> meetings;
	private List<Filter> filters;

	/**
	 * Parameter Constructor
	
	 * @param name String
	 */
	public User(String name) {
		this.id = -1;
		this.name = name;
		this.folders = new ArrayList<Folder>();
		this.tokens = new ArrayList<Token>();
		this.templates = new ArrayList<Template>();
		this.meetings = new ArrayList<Meeting>();
		this.filters = new ArrayList<Filter>();
		this.dao = DaoFactory.getUserDao();
	}
	
	/**
	 * Gets the user id
	
	 * @return id: numeric unique value for the user */
	public int getId() {
		return id;
	}


	/**
	 * Gets the user name
	
	 * @return name: name of the user */
	public String getName() {
		return name;
	}

	/**
	 * Sets the user's name
	
	 * @param name String
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Loads a user from the database given his name
	 * @param username
	
	
	 * @return boolean value indicating a successful or failed operation * @throws ModelException  */
	public boolean load(String username) throws ModelException{
		
		boolean toReturn = false;
		try {
			HashMap<String, String> result = dao.findByName(username);
			if (result != null){
				setProperties(result);
				toReturn = true;
			} else {
				toReturn = false;
			}
		} catch (DaoException e) {
			throw new ModelException(e.getMessage());
		}
		return toReturn;
	}
	
	/**
	 * Sets the properties given a HashMap<String, String>
	
	 * @param properties HashMap<String,String>
	 */
	public void setProperties(HashMap<String, String> properties){
		this.id = Integer.valueOf(properties.get("Id"));
		this.name = properties.get("Name");
	}
	

	/**
	 * Returns the current user properties in the form of a map
	
	 * @return hash map representation of user properties */
	private HashMap<String, String> getTransferObject() {

		HashMap<String, String> properties = new HashMap<String, String>();
		properties.put("Id", String.valueOf(id));
		properties.put("Name", name);
		return properties;
	}

	
	/**
	 * If the ID is already set, it updates the current user object. If the ID is not set,
	 * it creates a new user object and saves the id
	
	 * @throws ModelException  */
	public void save() throws ModelException{
		
		if (id == -1){
			// Create new and set the ID
			try {
				id = dao.create( getTransferObject() );
			} catch (DaoException e) {
				throw new ModelException(e.getMessage());
			}
			
		} else {
			
			// Save the current one
			try {
				dao.update( getTransferObject() );
			} catch (DaoException e) {
				throw new ModelException(e.getMessage());
			}
		}
	}

	/*
	 * ***********Functions for user folders*************
	 */
	
	/**
	 * Create a new folder
	
	
	
	 * @param name String
	 * @param parentId int
	 * @return Folder
	 * @throws ModelException  */
	public Folder createNewFolder(String name, int parentId) throws ModelException {
		
		// Check if the folder name already exists
		if (folderExists(name)){
			return null;
		}
		
		// Create the new folder at the top level
		Folder folder =  new Folder(name, id, parentId);
				
		// try to save
		folder.save();
		
		folders.add(folder);
		
		return folder;
	}
	
	/**
	 * Checks if a folder exists given its name
	
	
	 * @param folderName String
	 * @return boolean value indicating that the folder exists or not */
	public boolean folderExists(String folderName){
		
		for(Folder folder : folders){
			if (folder.getName().equalsIgnoreCase(folderName)){
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Given a folder ID, gives the level of the folder
	
	
	 * @param id int
	 * @return level of the folder */
	public int getFolderLevel(int id){
		
		if (id == 0){
			return 0;
		}
		
		int level = 1;
		Folder currentFolder = getFolder(id);
		while (true){
			if (getParentFolder(currentFolder.getId()) == null){
				break;
			} else {
				currentFolder = getParentFolder(currentFolder.getId());
				level++;
			}
		}
		return level;
	}
	
	/**
	 * Returns the folder object given an id
	
	
	 * @param id int
	 * @return Folder: object representing a folder */
	public Folder getFolder(int id){
		for (Folder f : folders){
			if (f.getId() == id){
				return f;
			}
		}
		return null;
	}
	
	/**
	 * Returns the folder object given a name
	
	
	 * @param name String
	 * @return Folder: folder object if found or null if not found */
	public Folder getFolder(String name){
		for (Folder f : folders){
			if (f.getName().trim().equalsIgnoreCase(name.trim())){
				return f;
			}
		}
		return null;
	}
	
	
	/**
	 * Returns the parent folder object given a folder id
	
	
	 * @param id int
	 * @return Folder: parent folder if found or null if not found */
	public Folder getParentFolder(int id){
		for (Folder f : folders){
			if (f.getId() == id){
				return getFolder(f.getParentId());
			}
		}
		return null;
	}
	
	/**
	 * Returns the folder's child objects given an id
	
	
	 * @param id int
	 * @return List of children folders */
	public List<Folder> getChildFolders(int id){
		
		List<Folder> toReturn = new ArrayList<Folder>();
		for (Folder f : folders){
			if (f.getParentId() == id){
				toReturn.add(f);
			}
		}
		return toReturn;
	}
	
	/**
	 * Gets all folders belonging to the user
	
	 * @return List of all folders belonging to a user */
	public List<Folder> getFolders() {
		return folders;
	}

	/**
	 * Recursively deletes a folder and all it's internal folders and emails
	
	
	 * @param folderId int
	 * @throws ModelException  */
	public void deleteFolder(int folderId) throws ModelException {
		
		Folder folder = getFolder(folderId);
		List<Folder> folderChildren = getChildFolders(folderId);
		
		// Delete all child folders
		for (Folder childFolder : folderChildren){
			deleteFolder(childFolder.getId());
		}
		
		// Delete all emails of this folder
		try {
			folder.deleteEmails();
		} catch (ModelException e) {
			throw new ModelException("Could not delete emails from folder : " + folder.getName());
		}
		
		// Delete the folder itself
		folder.delete();
		folders.remove(getFolder(folderId));
	}

	/**
	 * Sets all folders belonging to the user
	
	 * @param folders List<Folder>
	 */
	public void setFolders(List<Folder> folders) {
		this.folders = folders;
	}
	
	/*
	 * ***********Functions for user tokens*************
	 */
	
	/**
	 * Create a new token
	
	
	
	 * @param key String
	 * @param value String
	 * @return Token
	 * @throws ModelException  */
	public Token createNewToken(String key, String value) throws ModelException {

		// Create the new token at the top level
		Token token =  new Token(id, key, value);
				
		// try to save
		token.save();
		
		tokens.add(token);
		
		return token;
	}
	
	/**
	 * Checks if a token exists given its name
	
	
	 * @param tokenKey String
	 * @return boolean value indicating that the token exists or not */
	public boolean tokenExists(String tokenKey){
		
		for(Token token : tokens){
			if (token.getKey().equalsIgnoreCase(tokenKey)){
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Returns the token object given an id
	
	
	 * @param id int
	 * @return Token: object representing a token */
	public Token getToken(int id){
		for (Token token : tokens){
			if (token.getId() == id){
				return token;
			}
		}
		return null;
	}
	
	/**
	 * Returns the token object given a key
	
	
	 * @param key String
	 * @return Token: token object if found or null if not found */
	public Token getToken(String key){
		for (Token token : tokens){
			if (token.getKey().trim().equalsIgnoreCase(key.trim())){
				return token;
			}
		}
		return null;
	}

	/**
	 * Gets all tokens belonging to the user
	
	 * @return List of all tokens belonging to a user */
	public List<Token> getTokens() {
		return tokens;
	}

	/**
	 * Deletes a token given its key
	 * 
	 * @param key Token ken
	
	 * @throws ModelException */
	public void deleteToken(String key) throws ModelException {
		
		Token token = getToken(key);
		
		// Delete the token from the database and from the list
		token.delete();
		tokens.remove(token);
	}

	/**
	 * Sets all tokens belonging to the user
	
	 * @param tokens List<Token>
	 */
	public void setTokens(List<Token> tokens) {
		this.tokens = tokens;
	}
	
	/**
	 * Get the hashmap of all the tokens for the user
	
	 * @return HashMap<key,value> of the tokens */
	public HashMap<String,String> getTokenHashMap(){
		HashMap<String,String> tokensMap = new HashMap<String,String>();
		for(Token token : tokens){
			tokensMap.put(token.getKey(), token.getValue());
		}
		return tokensMap;
	}
	
	/**
	 * Given a list of ids, loops and deletes them from the tokens list and also from db
	 * @param tokenIdsToDelete
	
	 * @throws ModelException */
	public void deleteTokens(Set<Integer> tokenIdsToDelete) throws ModelException{
		// delete from db first and if successful, remove from the list of tokens as well
		try {
			DaoFactory.getTokenDao().deleteByTokenIds(tokenIdsToDelete);
		} catch (DaoException e) {
			throw new ModelException(e.getMessage());
		}
		for (ListIterator<Token> iter = tokens.listIterator(); iter.hasNext(); ){
			Token token = iter.next();
			if (tokenIdsToDelete.contains(Integer.valueOf(token.getId()))){
				iter.remove();
			}
		}		
	}
	
	/*
	 * ***********Functions for user templates*************
	 */

	/**
	 * Create a new meeting
	
	
	
	 * @param title String
	 * @param body String
	 * @return Template
	 * @throws ModelException  */
	public Template createNewTemplate(String title, String body) throws ModelException {
		
		// Check if the template title already exists
		if (templateExists(title)){
			return null;
		}
		
		// Create the new template at the top level
		Template template =  new Template(id, title, body);
				
		// try to save
		template.save();
		
		templates.add(template);
		
		return template;
	}
	
	/**
	 * Checks if a template exists given its name
	
	
	 * @param templateTitle String
	 * @return boolean value indicating that the template exists or not */
	public boolean templateExists(String templateTitle){
		
		for(Template template : templates){
			if (template.getTitle().equalsIgnoreCase(templateTitle)){
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Returns the template object given an id
	
	
	 * @param id int
	 * @return Template: object representing a template */
	public Template getTemplate(int id){
		for (Template template : templates){
			if (template.getId() == id){
				return template;
			}
		}
		return null;
	}
	
	/**
	 * Returns the template object given a title
	
	
	 * @param title String
	 * @return Template: template object if found or null if not found */
	public Template getTemplate(String title){
		for (Template template : templates){
			if (template.getTitle().trim().equalsIgnoreCase(title.trim())){
				return template;
			}
		}
		return null;
	}

	/**
	 * Gets all templates belonging to the user
	
	 * @return List of all templates belonging to a user */
	public List<Template> getTemplates() {
		return templates;
	}

	/**
	 * Delete template
	
	
	 * @param templateId int
	 * @throws ModelException  */
	public void deleteTemplate(int templateId) throws ModelException {
		
		Template template = getTemplate(templateId);
		
		// Delete the template from the database and from the list
		template.delete();
		templates.remove(template);
	}

	/**
	 * Sets all templates belonging to the user
	
	 * @param templates List<Template>
	 */
	public void setTemplates(List<Template> templates) {
		this.templates = templates;
	}
	
	/**
	 * Get the hashmap of all the templates for the user
	
	 * @return HashMap<title,body> of the templates */
	public HashMap<String,String> getTemplateHashMap(){
		HashMap<String,String> templatesMap = new HashMap<String,String>();
		for(Template template : templates){
			templatesMap.put(template.getTitle(), template.getBody());
		}
		return templatesMap;
	}
	
	/**
	 * Given a list of ids, loops and deletes them from the templates list and also from db
	 * @param templateIdsToDelete
	
	 * @throws ModelException */
	public void deleteTemplates(Set<Integer> templateIdsToDelete) throws ModelException{
		// delete from db first and if successful, remove from the list of templates as well
		try {
			DaoFactory.getTemplateDao().deleteByTemplateIds(templateIdsToDelete);
		} catch (DaoException e) {
			throw new ModelException(e.getMessage());
		}
		for (ListIterator<Template> iter = templates.listIterator(); iter.hasNext(); ){
			Template template = iter.next();
			if (templateIdsToDelete.contains(Integer.valueOf(template.getId()))){
				iter.remove();
			}
		}		
	}
	
	/*
	 * ***********Functions for user meetings*************
	 */

	/**
	 * Create a new meeting
	
	
	
	
	
	
	
	 * @param topic String
	 * @param location String
	 * @param start Timestamp
	 * @param end Timestamp
	 * @param attendees String
	 * @param description String
	 * @return Meeting
	 * @throws ModelException  */
	public Meeting createNewMeeting(String topic, String location, Timestamp start,
			Timestamp end, String attendees, String description) throws ModelException {
		
		
		// Create the new meeting at the top level
		Meeting meeting =  new MeetingBuilder()
							.setUserId(this.id)
							.setTopic(topic)
							.setLocation(location)
							.setStart(start)
							.setEnd(end)
							.setAttendees(attendees)
							.setDescription(description)
							.build();
				
		// try to save
		meeting.save();
		
		meetings.add(meeting);
		
		return meeting;
	}
	
	/**
	 * Returns the meeting object given an id
	
	
	 * @param id int
	 * @return Meeting: object representing a meeting */
	public Meeting getMeeting(int id){
		for (Meeting meeting : meetings){
			if (meeting.getId() == id){
				return meeting;
			}
		}
		return null;
	}
	
	/**
	 * Returns all the meeting objects for given a topic
	
	
	 * @param topic String
	 * @return List<Meeting>: list meeting objects if found or null if not found */
	public List<Meeting> getMeeting(String topic){
		List<Meeting> selectedMeetings = new ArrayList<Meeting>();
		for (Meeting meeting : meetings){
			if (meeting.getTopic().trim().equalsIgnoreCase(topic.trim())){
				selectedMeetings.add(meeting);
			}
		}
		return selectedMeetings;
	}

	/**
	 * Delete meeting
	
	
	 * @param meetingId int
	 * @throws ModelException  */
	public void deleteMeeting(int meetingId) throws ModelException {
		
		Meeting meeting = getMeeting(meetingId);
		
		// Delete the meeting from the database and from the list
		meeting.delete();
		meetings.remove(meeting);
	}

	/**
	 * Gets all meetings belonging to the user
	
	 * @return List of all meetings belonging to a user */
	public List<Meeting> getMeetings() {
		return meetings;
	}

	/**
	 * Sets all meetings belonging to the user
	
	 * @param meetings List<Meeting>
	 */
	public void setMeetings(List<Meeting> meetings) {
		this.meetings = meetings;
	}
	
	
	/*
	 * ***********Functions for user filters*************
	 */

	/**
	 * Create a new filter
	
	
	
	
	
	
	 * @param title String
	 * @param to String
	 * @param from String
	 * @param body String
	 * @param folderId int
	 * @return Filter
	 * @throws ModelException  */
	public Filter createNewFilter(String title, String to, String from,
			String body, String subject,int folderId) throws ModelException {
		// Create the new filter at the top level
		Filter filter =  new FilterBuilder()
							.setUserId(this.id)
							.setTitle(title)
							.setTo(to)
							.setFrom(from)
							.setBody(body)
							.setFolderId(folderId)
							.build();
		filter.setSubject(subject);
				
		// try to save
		filter.save();
		
		filters.add(filter);
		
		return filter;
	}
	
	/**
	 * Returns the filter object given an id
	
	
	 * @param id int
	 * @return Filter: object representing a filter */
	public Filter getFilter(int id){
		for (Filter filter : filters){
			if (filter.getId() == id){
				return filter;
			}
		}
		return null;
	}
	
	/**
	 * Returns all the filter objects for given a title
	
	
	 * @param title String
	 * @return Filter: first filter object found or null if not found */
	public Filter getFilter(String title){
		for (Filter filter : filters){
			if (filter.getTitle().trim().equalsIgnoreCase(title.trim())){
				return filter;
			}
		}
		return null;
	}

	/**
	 * Delete filter
	
	
	 * @param filterId int
	 * @throws ModelException  */
	public void deleteFilter(int filterId) throws ModelException {
		
		Filter filter = getFilter(filterId);
		
		// Delete the filter from the database and from the list
		filter.delete();
		filters.remove(filter);
	}

	/**
	 * Gets all filters belonging to the user
	
	 * @return List of all filters belonging to a user */
	public List<Filter> getFilters() {
		return filters;
	}

	/**
	 * Sets all filters belonging to the user
	
	 * @param filters List<Filter>
	 */
	public void setFilters(List<Filter> filters) {
		this.filters = filters;
	}
	
	/**
	 * This function will search all the folders and find emails where
	 * to, from and body fields match. If any of the parameters are passed
	 * empty, that field will not be searched for 
	 * 
	 * @param to
	 * @param from
	 * @param body
	
	 * @param subject String
	 * @return List of email that meets the criteria */
	public List<Email> searchEmails(String to, String from, String subject, String body) {
		
		List<Email> emails = new ArrayList<Email>();
		for (Folder folder : folders){
			for (Email email : folder.getEmails()) {
				
//				if((email.getSubject().trim().toLowerCase().contains(subject.trim().toLowerCase()))
//						||((email.getTo().trim().toLowerCase().contains(to.trim().toLowerCase())))
//						||((email.getFrom().trim().toLowerCase().contains(to.trim().toLowerCase())))
//						||((email.getBody().trim().toLowerCase().contains(body.trim().toLowerCase()))))
//					emails.add(email);
					
			
				if (!to.equals("")) {
					if (!email.getTo().trim().toLowerCase().contains(to.trim().toLowerCase())){
						continue;
					}
				}
				
				if (!from.equals("")) {
					if (!email.getFrom().trim().toLowerCase().contains(from.trim().toLowerCase())){
						continue;
					}
				}
				
				if (!subject.equals("")) {
					if (!email.getSubject().trim().toLowerCase().contains(subject.trim().toLowerCase())){
						continue;
					}
				}
				
				if (!body.equals("")) {
					if (!email.getBody().trim().toLowerCase().contains(body.trim().toLowerCase())){
						continue;
					}
				}
				
				if (to.equals("") && from.equals("") && subject.equals("") && body.equals("")) {
					continue;
				}
				emails.add(email);
			
		}}
		
		return emails;
	}
	
	/**
	 * This function will search all the folders given the field to search 
	 * within and the search string 
	 * 
	
	
	
	
	 * @param whereToSearch String
	 * @param search String
	 * @return List of email that meets the criteria */
	public List<Email> searchEmails(String whereToSearch, String search) {
		List<Email> emails = new ArrayList<Email>();
		for (Folder folder : folders){
			for (Email email : folder.getEmails()) {
				switch(whereToSearch) {
					case "to" : case "To" :
						if (email.getTo().trim().toLowerCase().contains(search.trim().toLowerCase())) {
							emails.add(email);
						}
						break;
					case "from" : case "From" :
						if (email.getFrom().trim().toLowerCase().contains(search.trim().toLowerCase())) {
							emails.add(email);
						}
						break;
					case "subject" : case "Subject" :
						if (email.getSubject().trim().toLowerCase().contains(search.trim().toLowerCase())) {
							emails.add(email);
						}
						break;
					case "body" : case "Body" :
					if (email.getBody().trim().toLowerCase().contains(search.trim().toLowerCase())) {
						emails.add(email);
					}
					break;
				}
			}
		}
		return emails;
	}

	/**
	 * Loops through all the folders and tries to get this email
	 * 
	 * @param id of the email
	
	 * @return Email objects */
	public Email getEmail(Integer id) {
		for(Folder folder : folders){
			for (Email email : folder.getEmails()){
				if (email.getId() == id){
					return email;
				}
			}
		}
		return null;
	}
	/**
	 * This method checks if the current meeting's start/end time overlap with
	 * an esisting meeting
	
	
	
	 * @param meeting Meeting
	 * @return boolean
	 */
	public boolean checkIfDatesOverlap(Meeting meeting)
	{
		for(Meeting mt: this.getMeetings())
		{
			if(meeting.hasSameDates(mt)|| (meeting.datesOverLap(mt)))
				return true;
		}

		return false;
	}
	
}
